#ifndef __CENTRALCACHE_HPP__
#define __CENTRALCACHE_HPP__
// 中央缓存，当前进程只能存在一个
// 饿汉模式设计，不存在线程安全问题

#include "Common.hpp"
#include "PageCache.hpp"

namespace QiHai
{
	class CentralCache
	{
	private:
		static CentralCache _obj;
		SpanList _spans[NFREELIST];

		CentralCache(){};
		CentralCache(CentralCache&) = delete;
		CentralCache& operator=(const CentralCache&) = delete;
	public:
		// 获取当前进程唯一的中央缓存对象
		static CentralCache* CentralCacheObj()
		{
			return &_obj;
		}
		// 申请
		size_t FetchRangeObj(size_t byte, void*& start, size_t exNum);
		Span* GetOneSpan(SpanList& list, size_t byte);

		// 释放
		void ReleaseListToSpans(void* start, size_t byte);
	};

	CentralCache CentralCache::_obj;  // 直接定义对象

	/////////////CentralCache///////////////

	// 申请对于对齐大小 期望exNum块链接起来的自由链表
	size_t CentralCache::FetchRangeObj(size_t byte, void*& start, size_t exNum)
	{
		// 在centralcache中，管理内存也是由哈希桶决定，只不过每一部分由span - 
		// 一段内存管理类型，里面存在一大段对应内存块的，这么设计是为了方便回收的时候解决外碎片问题

		// 首先，我们需要保证获得到一块存在剩余内存的span（给接口GetOneSpan去做，因为还存在申请插入问题），这里专心解决切分span问题
		size_t index = SizeClass::Index(byte);
		_spans[index].lock();
		Span* span = GetOneSpan(_spans[index], byte);

		// 申请到的span，有多少就切分多少，最多期望个数

		void* res = span->_freeList;
		start = res;
		assert(start);  // 这里出问题，说明GetOneSpan出错了

		size_t actualNum = 1;  // 实际个数
		while (actualNum < exNum && NextObj(res))
		{
			++actualNum;
			res = NextObj(res);
		}
		span->_freeList = NextObj(res);
		NextObj(res) = nullptr;
		span->_useCount += actualNum;

		// 注意上面涉及了对共享资源的操作，需要加上互斥锁进行一个保护
		_spans[index].unlock();
		return actualNum;
	}

	// 保证返回一个span，没有剩余的了就向上层进行申请
	Span* CentralCache::GetOneSpan(SpanList& list, size_t byte)
	{
		// 首先检测list是否存在span有剩余内存
		auto res = list.Begin();
		while (res != list.End())
		{
			if (res->_freeList) return res;
			res = res->_next;
		}

		// 表示当前哈希桶里的span不存在或者不可用
		// 需要向上层申请span了。
		// 这里需要根据byte，计算出应该需要多大的页page
		// 注意，这里就需要去pagecache申请了，所以此时可以将桶锁释放了（进这个函数前是加了锁的），可以方便释放的无需等待
		list.unlock();
		PageCache::PageCacheObj()->lock();  // 加大锁
		Span* span = PageCache::PageCacheObj()->NewSpan(SizeClass::NumMovePage(byte));
		PageCache::PageCacheObj()->unlock();

		// 将新的span的内存块根据类型大小进行切割，挂在_freeList上，供下层调用切分想要的个数
		span->_objNum = byte;
		span->_freeList = (void*)(span->_pageNum << SHIFT_PAGE);  // 计算出起始地址
		char* begin = (char*)(span->_freeList);
		size_t bytes = (span->_n << SHIFT_PAGE);
		char* end = begin + bytes;  // 计算结尾的位置

		void* it = begin;
		begin += byte;
		int i = 0;  // 测试个数
		while (begin < end)
		{
			i++;
			NextObj(it) = begin;
			begin += byte;
			it = NextObj(it);
		}
		NextObj(it) = nullptr;
		list.lock();  // 别忘了重新加上  上面是一个对局部变量的操作，所以不存在线程安全问题
		// 添加到双链表上并且返回此span
		list.PushBack(span);
		return span;
	}

	// 根据ThreadCache传回来的一段自由链表，根据其映射对应的span进行一个插入即可。当存在一个span满了就返回哦
	void CentralCache::ReleaseListToSpans(void* start, size_t byte)
	{
		// 根据传过来的start，一个一个进行查找自己对应的span插入 - 因为同一个类型可能回申请很多span哦
		// 首先找到自己对应的哈希桶
		size_t index = SizeClass::Index(byte);
		_spans[index].lock();
		void* res = start;
		while (res)
		{
			Span* span = PageCache::PageCacheObj()->MapObjectToSpan(res);
			void* next = NextObj(res);
			// 头插
			NextObj(res) = span->_freeList;
			span->_freeList = res;
			span->_useCount--;

			// 如果当前span全部还回来了
			if (span->_useCount == 0)
			{
				// 将此span上还给PageCache
				_spans[index].Erase(span);  // 先删除掉哦
				span->_freeList = nullptr;
				span->_next = span->_pre = nullptr;

				_spans[index].unlock();
				PageCache::PageCacheObj()->lock();
				PageCache::PageCacheObj()->ReleaseSpanToPageCache(span);
				PageCache::PageCacheObj()->unlock();
				_spans[index].lock();
			}
			res = next;
		}
		_spans[index].unlock();
	}
}

#endif // !__CENTRALCACHE_HPP__

